package com.onmicro.snc_ble.core.bluetooth;


import static java.lang.System.arraycopy;

/**
 * @author PengYaQuan
 * @create 2019-11-05 17:33
 * @Describe
 */
public class OmrUtils {
    private static final int PREAMBLE_LENGTH = 1;
    private static final int ADDRESS_LENGTH = 5;
    private static final int GUARD_LENGTH = 2;
    private static final int CRC_LENGTH = 2;
    /**
     * 泰凌payload
     */
    private static final int PAYLOAD_LENGTH = 8;
    /**
     * 开始位置
     */
    private static int PREAMBLE_START = 0;
    private static int ADDRESS_START = PREAMBLE_START + PREAMBLE_LENGTH;
    private static int GUARD_START = ADDRESS_START + ADDRESS_LENGTH;
    private static int PAYLOAD_START = GUARD_START + GUARD_LENGTH;
    /**
     * 加上 company ID length
     */
    private static final int BLE_COMPANY_ID_LENGTH = 2;
    private static final int BLE_ADVERTISING_PDU_HEADER_LENGTH = 2;
    private static final int BLE_ADVERTISING_PDU_PAYLOAD_ADV_IND_ADVA_LENGTH = 6;
    private static final int BLE_ADVERTISING_PDU_PAYLOAD_ADV_IND_ADVDATA_AD_STRUCTURE1_OF_FLAGS_LENGTH = 3;
    private static final int BLE_ADVERTISING_PDU_PAYLOAD_ADV_IND_ADVDATA_AD_STRUCTURE2_OF_SERVICEDATAORMANUFACTUREDATA_LEADING_LENGTH = 2;

    private static byte[] mBytes;
    /**
     * {2402, 2426, 2480} MHz
     */
    private static final int[] ADVERTISING_CHANNEL = {37, 38, 39};

    public static void getRfPayload(byte[] omrRfAddr, int addrLen, byte[] payloadData, int dataLen, byte[] advertisingData) {
        byte[] userData = getOmrRfPacketIosCompatible(omrRfAddr, payloadData, dataLen);
        // reverse
        userData = bitReverseInByte(userData, 0, userData.length);
        // white
        userData = bleWhiteningForRfPacket(userData, ADVERTISING_CHANNEL[0]);
        arraycopy(userData, 0, advertisingData, 0, userData.length);
    }

    private static byte[] getOmrRfPacketIosCompatible(byte[] address, byte[] data, int dataLen) {
        mBytes = new byte[packetLength()];
        fillZero();
        // preamble
        mBytes[PREAMBLE_START] = ((address[0] & 0x80) == 0x80) ? (byte) 0xAA : 0x55;
        // address
        arraycopy(address, 0, mBytes, ADDRESS_START, Math.min(address.length, ADDRESS_LENGTH));
        // guard
        for (int i = 0; i < GUARD_LENGTH; i++) {
            mBytes[GUARD_START + i] = mBytes[GUARD_START - 1];
        }
        // payload
        arraycopy(data, 0, mBytes, PAYLOAD_START, Math.min(data.length, PAYLOAD_LENGTH));
        // crc
        int crc16;
        crc16 = OmrCRC16.get_ccitt_crc16(mBytes, ADDRESS_START, ADDRESS_LENGTH, 0xFFFF);
        crc16 = OmrCRC16.get_ccitt_crc16(mBytes, PAYLOAD_START, PAYLOAD_LENGTH, crc16);
        mBytes[crcStart()] = (byte) ((crc16 >>> 8) & 0xFF);
        mBytes[crcStart() + 1] = (byte) (crc16 & 0xFF);
        return mBytes;
    }

    private static int packetLength() {
        return PREAMBLE_LENGTH + ADDRESS_LENGTH + GUARD_LENGTH + PAYLOAD_LENGTH + CRC_LENGTH;
    }

    private static void fillZero() {
        for (int i = 0; i < mBytes.length; i++) {
            mBytes[i] = 0x00;
        }
    }

    private static int crcStart() {
        return PAYLOAD_START + PAYLOAD_LENGTH;
    }

    /**
     * Below are inernal private constructional functions.
     */
    private static byte[] bitReverseInByte(byte[] in_data, final int start, final int length) {
        byte[] out_data = new byte[in_data.length];

        for (int i = 0; i < in_data.length; i++) {
            int x = 0;
            for (int j = 0; j < 8; j++) {
                x += (((in_data[i] & 0xff) >>> (8 - 1 - j)) & 0x1) << j;
            }
            out_data[i] = (byte) x;
        }

        return out_data;
    }

    /**
     * white for rf packet ,
     */
    private static byte[] bleWhiteningForRfPacket(final byte[] rf_packet, final int channel) {
        final int whitening_omittedBytes
                = BLE_COMPANY_ID_LENGTH
                + BLE_ADVERTISING_PDU_HEADER_LENGTH
                + BLE_ADVERTISING_PDU_PAYLOAD_ADV_IND_ADVA_LENGTH
                + BLE_ADVERTISING_PDU_PAYLOAD_ADV_IND_ADVDATA_AD_STRUCTURE1_OF_FLAGS_LENGTH
                + BLE_ADVERTISING_PDU_PAYLOAD_ADV_IND_ADVDATA_AD_STRUCTURE2_OF_SERVICEDATAORMANUFACTUREDATA_LEADING_LENGTH;

        return bleWhiteningEx(rf_packet, channel, whitening_omittedBytes);
    }

    private static byte[] bleWhiteningEx(byte[] rf_packet, int channel, int whitening_omittedBytes) {
        byte[] b = new byte[whitening_omittedBytes + rf_packet.length];
        arraycopy(rf_packet, 0, b, whitening_omittedBytes, rf_packet.length);
        b = bleWhitening(b, channel);

        byte[] out_data = new byte[rf_packet.length];
        arraycopy(b, whitening_omittedBytes, out_data, 0, rf_packet.length);
        return out_data;
    }

    static private byte[] bleWhitening(final byte[] in_data, final int channel) {
        // Do linear feedback shift register (LFSR)
        int seed = 0x01;                  // Position0 = 1;
        seed |= (channel & (1 << 5)) >>> 4; // Position1;
        seed |= (channel & (1 << 4)) >>> 2; // Position2;
        seed |= (channel & (1 << 3)) << 0; // Position3;
        seed |= (channel & (1 << 2)) << 2; // Position4;
        seed |= (channel & (1 << 1)) << 4; // Position5;
        seed |= (channel & (1 << 0)) << 6; // Position6;
        seed &= 0x00FF;

        byte[] out_data = new byte[in_data.length];
        for (int i = 0; i < in_data.length; ++i) {
            int out = 0;
            for (int j = 0; j < 8; ++j) {
                seed &= 0x00FF;
                int factor = (seed & (1 << 6)) >>> 6;
                out |= ((in_data[i] & 0x00FF) ^ (factor << j)) & (1 << j);
                seed <<= 1;
                int shift_out = (seed >>> 7) & 0x01;
                seed = (seed & ~(1 << 0)) | shift_out;
                seed = (seed & ~(1 << 4)) | ((seed ^ (shift_out << 4)) & (1 << 4));
            }
            out_data[i] = (byte) out;
        }
        return out_data;
    }


    /**
     * Convert a byte array to a String separated by a specified separator.
     *
     * @param b         Byte array to be converted.
     * @param separator Used to separate chars in the resulting String.
     * @return The converted String.
     */
    public static String getHexString(byte[] b, String separator) {
        if (b == null)
            return "";

        String result = "";
        for (int i = 0; i < b.length; i++) {
            result += Integer.toString((b[i] & 0xff) + 0x100, 16).substring(1);
            if (separator != null && !separator.isEmpty() && i != b.length - 1)
                result += separator;
        }
        return result;
    }
}
